import { ref, computed, watch, onUnmounted, type Ref } from 'vue'
import { useQuasar } from 'quasar'
import { WebRtcConnectionManager, publishToSrs, playFromSrs, checkStreamPlayers } from '@/services/webrtc-connection'
import { WebRtcLogger } from '@/services/webrtc-logger'
import { isTauri } from '@/services/secure-storage'
import { startCallRingtone, stopCallRingtone } from '@/utils/notification'

export interface GroupMember {
  member_id: string
  displayName: string
  alias?: string
  avatar?: string
  role?: number
}

export interface VideoCallProps {
  modelValue: boolean
  targetUserId: string
  targetName: string
  currentUserId: string
  getUserInitial: (userId: string) => string
  isVoiceOnly?: boolean
  isGroupCall?: boolean // 是否是群聊通话
  groupMembers?: GroupMember[] // 群聊成员列表（用于呼叫界面显示）
}

export interface VideoCallEmits {
  'update:modelValue': [value: boolean]
  'call-ended': []
}

export function useVideoCall(
  props: VideoCallProps,
  emit: (event: any, ...args: any[]) => void,
  localVideoRef: Ref<HTMLVideoElement | null>,
  remoteVideoRef: Ref<HTMLVideoElement | null>
) {
  const $q = useQuasar()

  // 状态
  const isConnecting = ref(false)
  const loadingText = ref('正在连接...')
  const callStatus = ref('正在连接...')
  const isMuted = ref(false)
  const isVideoOff = ref(false)
  const isLocalVideoActive = ref(false)
  const isRemoteVideoActive = ref(false)
  const callDuration = ref(0) // 通话时长（秒）
  let callDurationTimer: number | null = null

  // WebRTC 相关
  let localStream: MediaStream | null = null
  let publishManager: WebRtcConnectionManager | null = null
  let playManager: WebRtcConnectionManager | null = null
  let isInitializing = false // 防止重复初始化
  let playerCheckInterval: number | null = null // 播放者检查轮询定时器
  let callTimeoutTimer: number | null = null // 通话超时定时器
  // 缓存轨道引用，确保在通话期间可以访问
  let cachedAudioTracks: MediaStreamTrack[] = []
  let cachedVideoTracks: MediaStreamTrack[] = []

  // 创建日志记录器
  const logger = new WebRtcLogger('VideoCall')

  // 流名称（使用用户ID组合，确保唯一性）
  const streamName = computed(() => {
    // 使用排序后的用户ID组合，确保双方使用相同的流名称
    const ids = [props.currentUserId, props.targetUserId].sort()
    return `${ids[0]}_${ids[1]}`
  })

  // isOpen computed
  const isOpen = computed({
    get: () => props.modelValue,
    set: (value) => emit('update:modelValue', value),
  })

  // 尝试初始化 mediaDevices polyfill（如果需要）
  const initMediaDevicesPolyfill = (): void => {
    if (typeof navigator === 'undefined') {
      return
    }

    // 如果 mediaDevices 已经存在且有 getUserMedia 方法，不需要 polyfill
    if (navigator.mediaDevices && typeof navigator.mediaDevices.getUserMedia === 'function') {
      return
    }

    // 首先检查 window.mediaDevices（某些环境可能在这里）
    if (typeof window !== 'undefined' && (window as any).mediaDevices && !navigator.mediaDevices) {
      ;(navigator as any).mediaDevices = (window as any).mediaDevices
      return
    }

    // 如果 mediaDevices 不存在或没有 getUserMedia，尝试创建 polyfill
    if (!navigator.mediaDevices || typeof navigator.mediaDevices.getUserMedia !== 'function') {
      // 检查是否有旧版 API（在 navigator 和 window 下都检查）
      const getUserMedia = 
        navigator.getUserMedia ||
        (navigator as any).webkitGetUserMedia ||
        (navigator as any).mozGetUserMedia ||
        (navigator as any).msGetUserMedia ||
        (typeof window !== 'undefined' && (window as any).navigator?.getUserMedia) ||
        (typeof window !== 'undefined' && (window as any).navigator?.webkitGetUserMedia) ||
        (typeof window !== 'undefined' && (window as any).navigator?.mozGetUserMedia) ||
        (typeof window !== 'undefined' && (window as any).navigator?.msGetUserMedia)

      if (getUserMedia) {
        // 如果 mediaDevices 对象已存在但没有 getUserMedia，添加它
        if (!navigator.mediaDevices) {
          ;(navigator as any).mediaDevices = {}
        }
        // 创建 mediaDevices 对象或添加 getUserMedia 方法
        ;(navigator as any).mediaDevices.getUserMedia = (constraints: MediaStreamConstraints) => {
          return new Promise((resolve, reject) => {
            getUserMedia.call(
              navigator,
              constraints,
              (stream: MediaStream) => resolve(stream),
              (error: any) => reject(error)
            )
          })
        }
        // 如果没有 enumerateDevices，添加一个空实现
        if (!navigator.mediaDevices.enumerateDevices) {
          ;(navigator as any).mediaDevices.enumerateDevices = () => {
            return Promise.resolve([])
          }
        }
      } else {
        // 只在第一次尝试时输出警告，避免日志过多
        if (!(initMediaDevicesPolyfill as any).warned) {
          logger.warn('未找到任何 getUserMedia API')
          ;(initMediaDevicesPolyfill as any).warned = true
        }
      }
    }
  }

  // 检查并等待 mediaDevices 可用
  const ensureMediaDevicesAvailable = async (): Promise<void> => {
    // 首先尝试初始化 polyfill
    initMediaDevicesPolyfill()

    // 在 Tauri 环境中，等待窗口完全加载
    if (isTauri() && typeof window !== 'undefined') {
      // 等待 DOMContentLoaded 或 load 事件（如果还没触发）
      if (document.readyState === 'loading') {
        await new Promise<void>((resolve) => {
          if (document.readyState === 'complete') {
            resolve()
          } else {
            const onReady = () => {
              document.removeEventListener('DOMContentLoaded', onReady)
              window.removeEventListener('load', onReady)
              resolve()
            }
            document.addEventListener('DOMContentLoaded', onReady)
            window.addEventListener('load', onReady)
            // 超时保护
            setTimeout(() => {
              document.removeEventListener('DOMContentLoaded', onReady)
              window.removeEventListener('load', onReady)
              resolve()
            }, 2000)
          }
        })
      }
      
      // 额外等待，确保 WebView 完全初始化
      await new Promise(resolve => setTimeout(resolve, 500))
    }

    // 检查 navigator.mediaDevices 是否可用
    // 使用更严格的检查：确保 mediaDevices 存在且 getUserMedia 是一个函数
    const checkMediaDevicesAvailable = (): boolean => {
      if (typeof navigator === 'undefined') {
        return false
      }
      
      // 检查 mediaDevices 是否存在
      if (!navigator.mediaDevices) {
        return false
      }
      
      // 检查 getUserMedia 是否存在且是函数
      if (typeof navigator.mediaDevices.getUserMedia !== 'function') {
        return false
      }
      
      return true
    }
    
    if (!checkMediaDevicesAvailable()) {
      // 在 Tauri 环境中，WebView 可能需要一些时间初始化
      // 重试次数改为 3 次，避免等待时间过长
      const maxAttempts = 3
      const waitInterval = isTauri() ? 200 : 100
      
      for (let i = 0; i < maxAttempts; i++) {
        await new Promise(resolve => setTimeout(resolve, waitInterval))
        
        // 每次等待后再次尝试初始化 polyfill
        initMediaDevicesPolyfill()
        
        // 检查 mediaDevices 是否可用
        if (checkMediaDevicesAvailable()) {
          return
        }
      }

      // 如果仍然不可用，提供详细的诊断信息
      const diagnostics = {
        hasNavigator: typeof navigator !== 'undefined',
        hasMediaDevices: typeof navigator !== 'undefined' && 'mediaDevices' in navigator,
        hasGetUserMedia: typeof navigator !== 'undefined' && 'getUserMedia' in navigator,
        hasWebkitGetUserMedia: typeof navigator !== 'undefined' && 'webkitGetUserMedia' in navigator,
        hasWindow: typeof window !== 'undefined',
        hasWindowNavigator: typeof window !== 'undefined' && window.navigator !== undefined,
        hasWindowMediaDevices: typeof window !== 'undefined' && (window as any).mediaDevices !== undefined,
        protocol: typeof window !== 'undefined' ? window.location.protocol : 'N/A',
        userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'N/A',
        isTauri: isTauri(),
      }
      
      logger.error('mediaDevices 不可用 - 诊断信息', diagnostics)

      // 如果仍然不可用，提供详细的错误信息和解决方案
      const errorMsg = isTauri()
        ? '❌ 无法访问媒体设备\n\n' +
          '🔍 诊断信息：\n' +
          `   • navigator 存在: ${diagnostics.hasNavigator ? '✅' : '❌'}\n` +
          `   • mediaDevices 存在: ${diagnostics.hasMediaDevices ? '✅' : '❌'}\n` +
          `   • 协议: ${diagnostics.protocol}\n` +
          `   • User Agent: ${diagnostics.userAgent?.substring(0, 50)}...\n\n` +
          '💡 可能的原因和解决方案：\n\n' +
          '1️⃣ 系统权限问题（最常见）\n' +
          '   macOS: 系统设置 > 隐私与安全性 > 摄像头/麦克风\n' +
          '   • 确保 "violet" 应用已被允许访问\n' +
          '   • 如果应用不在列表中，重启应用后系统会提示授权\n\n' +
          '2️⃣ WebView 初始化问题\n' +
          '   • 尝试关闭并重新打开视频通话窗口\n' +
          '   • 重启应用\n\n' +
          '3️⃣ 设备被占用\n' +
          '   • 关闭其他可能使用摄像头/麦克风的应用\n' +
          '   • 检查系统活动监视器\n\n' +
          '4️⃣ Tauri WebView 限制\n' +
          '   • 某些 Tauri WebView 版本可能不完全支持 mediaDevices API\n' +
          '   • 尝试更新 Tauri 到最新版本\n\n' +
          '📝 如果问题持续，请检查：\n' +
          '   • macOS 版本是否支持（需要 macOS 10.15+）\n' +
          '   • 是否有防火墙或安全软件阻止\n' +
          '   • 查看控制台的完整诊断信息'
        : '您的环境不支持媒体设备访问。\n\n' +
          '请检查：\n' +
          '1. 应用是否运行在安全上下文（HTTPS 或 localhost）中\n' +
          '2. 浏览器是否支持 WebRTC\n' +
          '3. 媒体设备权限是否已授予'
      
      throw new Error(errorMsg)
    }
  }

  // 初始化 WebRTC 连接
  const initWebRTC = async () => {
    // 防止重复初始化
    if (isInitializing) {
      logger.warn('WebRTC 正在初始化中，忽略重复请求')
      return
    }
    
    // 如果已有连接，先清理
    if (publishManager || playManager) {
      logger.warn('检测到已有连接，先清理旧连接')
      cleanup()
      // 等待清理完成
      await new Promise(resolve => setTimeout(resolve, 500))
    }
    
    isInitializing = true
    try {
      isConnecting.value = true
      loadingText.value = '正在获取媒体权限...'
      callStatus.value = '正在连接...'

      // 尝试确保 mediaDevices 可用
      // 在 Tauri 环境中，即使检查失败也尝试继续（因为可能在调用时才可用）
      try {
        await ensureMediaDevicesAvailable()
        logger.info('mediaDevices 检查通过，开始获取媒体流')
      } catch (error: any) {
        if (isTauri()) {
          // 在 Tauri 环境中，即使检查失败也尝试继续
          logger.warn('mediaDevices 检查失败，但在 Tauri 环境中仍尝试获取媒体流', error.message)
          // 最后一次尝试初始化 polyfill
          initMediaDevicesPolyfill()
        } else {
          // 非 Tauri 环境，直接抛出错误
          throw error
        }
      }

      // 获取本地媒体流
      const constraints: MediaStreamConstraints = {
        audio: true,
        video: !props.isVoiceOnly ? {
          width: { ideal: 1280 },
          height: { ideal: 720 },
        } : false,
      }

      // 在调用前再次检查，如果仍然不可用则抛出错误
      if (!navigator.mediaDevices || typeof navigator.mediaDevices.getUserMedia !== 'function') {
        const errorMsg = isTauri()
          ? '无法访问媒体设备。\n\n' +
            '请确保：\n' +
            '1. 应用已获得摄像头和麦克风权限（系统设置 > 隐私与安全性）\n' +
            '2. 没有其他应用占用摄像头或麦克风\n' +
            '3. 尝试重启应用'
          : '您的环境不支持媒体设备访问。'
        throw new Error(errorMsg)
      }

      logger.info('开始调用 getUserMedia...')
      try {
        localStream = await navigator.mediaDevices.getUserMedia(constraints)
        logger.info('getUserMedia 调用成功，已获取媒体流')
      } catch (getUserMediaError: any) {
        logger.error('getUserMedia 调用失败', getUserMediaError)
        throw getUserMediaError
      }
      
      // 设置本地视频流到本地视频元素
      // 添加延迟，确保 ref 已经正确同步
      await new Promise(resolve => setTimeout(resolve, 100))
      
      // 验证 ref 不是同一个元素
      if (localVideoRef.value && remoteVideoRef.value && localVideoRef.value === remoteVideoRef.value) {
        console.error('❌ [VideoCall] 检测到本地和远程视频 ref 指向同一个元素！', {
          element: localVideoRef.value,
          stack: new Error().stack
        })
        logger.error('检测到本地和远程视频 ref 指向同一个元素，这是错误的！')
        throw new Error('本地和远程视频 ref 不能指向同一个元素')
      }
      
      // 第一步：先确保远程视频元素在对方未接听时没有流
      if (remoteVideoRef.value) {
        if (remoteVideoRef.value.srcObject) {
          const currentStream = remoteVideoRef.value.srcObject as MediaStream
          if (currentStream === localStream) {
            console.error('❌ [VideoCall] 初始化时检测到远程视频元素被错误地设置为本地流，清除它', {
              streamId: currentStream.id,
              timestamp: new Date().toISOString()
            })
          }
          logger.warn('初始化时检测到远程视频元素已有流，清除它（对方尚未接听）')
          remoteVideoRef.value.srcObject = null
        }
        isRemoteVideoActive.value = false
      }
      
      // 第二步：设置本地视频流到本地视频元素
      if (localVideoRef.value && localStream) {
        // 确保只设置到本地视频元素，并且清除之前可能存在的流
        if (localVideoRef.value.srcObject) {
          const oldStream = localVideoRef.value.srcObject as MediaStream
          oldStream.getTracks().forEach(track => {
            // 不停止轨道，只是清除引用
          })
          localVideoRef.value.srcObject = null
        }
        localVideoRef.value.srcObject = localStream
        isLocalVideoActive.value = true
        logger.info('本地视频流已设置到本地视频元素', {
          localVideoElementId: localVideoRef.value.id || 'no-id',
          streamId: localStream.id
        })
      } else {
        logger.warn('本地视频元素或流不可用', {
          hasLocalVideoRef: !!localVideoRef.value,
          hasLocalStream: !!localStream
        })
      }
      
      // 第三步：再次验证，确保远程视频元素没有被错误地设置为本地流
      if (remoteVideoRef.value && localStream) {
        // 等待一小段时间，确保 DOM 更新完成
        await new Promise(resolve => setTimeout(resolve, 50))
        
        if (remoteVideoRef.value.srcObject === localStream) {
          console.error('❌ [VideoCall] 检测到远程视频元素被错误地设置为本地流，正在修复...', {
            streamId: localStream.id,
            timestamp: new Date().toISOString(),
            stack: new Error().stack
          })
          logger.error('检测到远程视频元素被错误地设置为本地流，正在修复...')
          remoteVideoRef.value.srcObject = null
          isRemoteVideoActive.value = false
        }
        
        // 验证本地视频元素确实有本地流
        if (localVideoRef.value && localVideoRef.value.srcObject !== localStream) {
          logger.warn('本地视频元素没有本地流，重新设置...')
          localVideoRef.value.srcObject = localStream
          isLocalVideoActive.value = true
        }
      }

      // 创建推流连接管理器
      loadingText.value = '正在建立推流连接...'
      publishManager = new WebRtcConnectionManager(
        streamName.value,
        props.isVoiceOnly,
        {
          onIceCandidate: () => {
          },
          onIceCandidateComplete: (candidates) => {
            logger.info(`推流 ICE 候选收集完成，共 ${candidates.length} 个候选`)
          },
          onIceConnectionStateChange: (state) => {
            if (state === 'failed' && publishManager) {
              try {
                const stats = publishManager.getStats()
                logger.error('推流 ICE 连接失败', stats)
                logger.error('可能的原因和解决方案:', {
                  localCandidates: stats.localCandidates.length,
                  remoteCandidates: stats.remoteCandidates.length,
                })
              } catch (error) {
                logger.error('获取推流状态失败', error)
              }
            }
          },
          onConnectionStateChange: (state) => {
            if (state === 'connected') {
              // 推流连接成功，表示自己已准备好，显示"呼叫中"
              callStatus.value = '呼叫中'
              isConnecting.value = false
              // 开始播放呼叫铃声
              startCallRingtone()
              // 开始轮询检查是否有播放者（对方是否拉流）
              startPlayerCheck()
              // 启动超时定时器（33秒后如果没接听，自动挂断）
              startCallTimeout()
            } else if (state === 'failed') {
              logger.error('推流连接失败')
            }
          },
          onError: (error) => {
            logger.error('推流错误', error)
            handleError(error.message)
          },
        }
      )

      // 检查推流管理器是否仍然存在（可能在创建后被清理）
      if (!publishManager) {
        logger.warn('推流管理器在创建后被清理，停止初始化')
        return
      }

      // 添加本地流
      publishManager.addLocalStream(localStream)
      
      // 缓存轨道引用，用于静音/视频切换（主流IM方案：直接控制轨道，不重新连接）
      cachedAudioTracks = localStream.getAudioTracks()
      cachedVideoTracks = localStream.getVideoTracks()
      logger.info(`缓存轨道: ${cachedAudioTracks.length} 个音频轨道, ${cachedVideoTracks.length} 个视频轨道`)

      // 再次检查（可能在添加流后被清理）
      if (!publishManager) {
        logger.warn('推流管理器在添加流后被清理，停止初始化')
        return
      }

      // 创建并发送推流 offer
      logger.info('创建推流 offer...')
      const publishOffer = await publishManager.createOffer({
        offerToReceiveAudio: false,
        offerToReceiveVideo: false,
      })

      // 再次检查（可能在创建 offer 后被清理）
      if (!publishManager) {
        logger.warn('推流管理器在创建 offer 后被清理，停止初始化')
        return
      }

      // 发送到 SRS
      logger.info('发送推流 offer 到 SRS...')
      const publishAnswerSdp = await publishToSrs(streamName.value, publishOffer)

      // 检查推流管理器是否仍然存在（可能在异步操作中被清理）
      if (!publishManager) {
        logger.warn('推流管理器在发送 offer 后被清理，停止初始化')
        return
      }

      // 设置远程描述
      logger.info('设置推流远程描述...')
      await publishManager.setRemoteDescription({
        type: 'answer',
        sdp: publishAnswerSdp,
      })

      // 再次检查（可能在设置远程描述后又被清理）
      if (!publishManager) {
        logger.warn('推流管理器在设置远程描述后被清理，停止初始化')
        return
      }

      // 等待连接建立
      logger.info('等待推流 ICE 连接建立...')
      const publishConnected = await publishManager.waitForIceConnection(10000)
      
      // 额外等待，确保 SRS 流已注册并准备好
      const waitTime = publishConnected ? 2000 : 3000
      
      if (!publishConnected) {
        logger.warn('推流连接未完全建立，但继续尝试拉流')
        logger.warn('说明：推流失败可能是网络问题，但不影响接收对方的音视频')
      }
      
      await new Promise(resolve => setTimeout(resolve, waitTime))
      
      // 检查推流连接状态
      if (!publishManager) {
        // 推流管理器已被清理，静默返回（可能是用户已关闭通话）
        logger.info('推流管理器已被清理，停止初始化')
        return
      }
      const publishStats = publishManager.getStats()
      
      if (publishStats.iceConnectionState === 'failed') {
        logger.warn('推流连接失败诊断:', {
          message: '这不是对方没接听导致的（对方接听只影响拉流）',
          possibleCauses: [
            'SRS 服务器 CANDIDATE 配置不正确',
            '网络连接问题（防火墙/NAT）',
            'SRS 服务器不可达',
          ],
        })
      }

      // 创建拉流连接管理器
      loadingText.value = '正在建立拉流连接...'
      playManager = new WebRtcConnectionManager(
        streamName.value,
        props.isVoiceOnly,
        {
          onIceCandidate: () => {
          },
          onIceCandidateComplete: (candidates) => {
            logger.info(`拉流 ICE 候选收集完成，共 ${candidates.length} 个候选`)
          },
          onIceConnectionStateChange: (state) => {
            if (state === 'failed' && playManager) {
              try {
                const stats = playManager.getStats()
                logger.error('拉流 ICE 连接失败', stats)
              } catch (error) {
                logger.error('获取拉流状态失败', error)
              }
            } else if (state === 'connected' || state === 'completed') {
              logger.info('拉流 ICE 连接已建立', state)
            }
          },
          onConnectionStateChange: (state) => {
            if (state === 'connected') {
              // 拉流连接成功，但还没收到轨道，保持"呼叫中"状态
              // 只有收到轨道时才更新为"已接听"
              if (callStatus.value === '正在连接...') {
                callStatus.value = '呼叫中'
              }
            } else if (state === 'failed') {
              logger.error('拉流连接失败')
            }
          },
          onTrack: (event) => {
            const remoteStream = event.streams[0]
            const isLocalStreamByRef = localStream && remoteStream === localStream
            const isLocalStreamById = localStream && remoteStream?.id === localStream.id
            const isCalling = callStatus.value === '呼叫中' // 对方未接听
            
            logger.info('收到拉流轨道', { kind: event.track.kind })
            
            // 关键检查1：如果收到的流对象引用是本地流，说明有问题
            if (isLocalStreamByRef) {
              console.error('❌ [VideoCall] 收到拉流轨道，但流对象引用是本地流！这是错误的！', {
                streamId: remoteStream.id,
                timestamp: new Date().toISOString(),
                stack: new Error().stack
              })
              logger.error('收到拉流轨道，但流对象引用是本地流！这是错误的！')
              // 不设置到远程视频元素，直接返回
              return
            }
            
            // 关键检查2：如果流 ID 和本地流 ID 相同，且对方未接听，这是 SRS 回环，应该拒绝
            // SRS 在对方未接听时，可能会返回自己的流（回环），流 ID 相同但对象不同
            if (isLocalStreamById && isCalling && localStream) {
              console.error('❌ [VideoCall] 收到拉流轨道，流 ID 和本地流相同且对方未接听！这是 SRS 回环，拒绝设置', {
                streamId: remoteStream.id,
                localStreamId: localStream.id,
                callStatus: callStatus.value,
                timestamp: new Date().toISOString(),
                stack: new Error().stack
              })
              logger.error('收到拉流轨道，流 ID 和本地流相同且对方未接听！这是 SRS 回环，拒绝设置')
              // 不设置到远程视频元素，直接返回，保持占位符显示
              return
            }
            
            // 收到远程轨道，表示对方已接听并开始发送音视频
            // 注意：状态更新由轮询机制负责，这里只处理媒体流
            
            // 关键检查3：在对方未接听时，如果流 ID 和本地流相同，这是 SRS 回环，拒绝处理
            // 只有在对方已接听（callStatus === '已接听'）时，才允许处理远程流
            // 或者流 ID 和本地流不同时，才允许处理
            if (isCalling && isLocalStreamById) {
              console.warn('⚠️ [VideoCall] 对方未接听，收到拉流轨道但流 ID 和本地流相同（SRS 回环），忽略此轨道', {
                streamId: remoteStream.id,
                callStatus: callStatus.value,
                timestamp: new Date().toISOString()
              })
              logger.warn('对方未接听，收到拉流轨道但流 ID 和本地流相同（SRS 回环），忽略此轨道')
              // 不处理此轨道，直接返回，保持占位符显示
              return
            }
            
            // 如果是视频通话且有视频轨道，设置到远程 video 元素
            if (!props.isVoiceOnly && remoteStream) {
              // 确保只设置到远程视频元素，并且确保本地视频元素没有被错误设置
              if (remoteVideoRef.value) {
                const oldStream = remoteVideoRef.value.srcObject as MediaStream | null
                
                // 清除之前可能存在的流
                if (oldStream) {
                  const oldIsLocalStreamByRef = localStream && oldStream === localStream
                  const oldIsLocalStreamById = localStream && oldStream.id === localStream.id
                  
                  // 如果旧流是本地流（对象引用或 ID 相同），这是错误的，需要清除
                  if (oldIsLocalStreamByRef || oldIsLocalStreamById) {
                    console.error('❌ [VideoCall] 检测到远程视频元素之前被错误地设置为本地流！', {
                      streamId: oldStream.id,
                      timestamp: new Date().toISOString()
                    })
                  }
                  
                  oldStream.getTracks().forEach(track => {
                    // 不停止轨道，只是清除引用
                  })
                  remoteVideoRef.value.srcObject = null
                }
                
                // 再次验证：确保 remoteStream 不是本地流（对象引用和 ID 都要检查）
                // 这个检查已经在上面做了，但为了安全，在这里再次检查
                const isLocalStreamByRefCheck = localStream && remoteStream === localStream
                const isLocalStreamByIdCheck = localStream && remoteStream.id === localStream.id
                const isCallingNow = callStatus.value === '呼叫中'
                
                if (isLocalStreamByRefCheck) {
                  console.error('❌ [VideoCall] 准备设置的远程流对象引用是本地流！拒绝设置', {
                    streamId: remoteStream.id,
                    timestamp: new Date().toISOString(),
                    stack: new Error().stack
                  })
                  logger.error('准备设置的远程流对象引用是本地流！拒绝设置')
                  return
                }
                
                // 如果流 ID 相同且对方未接听，这是 SRS 回环，拒绝设置
                // 这是关键检查：防止 SRS 回环导致两个窗口都显示本地视频
                if (isLocalStreamByIdCheck && isCallingNow) {
                  console.error('❌ [VideoCall] 准备设置的远程流 ID 和本地流相同且对方未接听！这是 SRS 回环，拒绝设置', {
                    streamId: remoteStream.id,
                    callStatus: callStatus.value,
                    timestamp: new Date().toISOString(),
                    stack: new Error().stack,
                    explanation: 'SRS 在对方未接听时，拉流会返回自己的推流（回环），流 ID 相同但对象不同。这是正常的 SRS 行为，但我们应该拒绝设置到远程视频元素。'
                  })
                  logger.error('准备设置的远程流 ID 和本地流相同且对方未接听！这是 SRS 回环，拒绝设置')
                  // 不设置，保持 isRemoteVideoActive = false，占位符继续显示
                  return
                }
                
                // 最终验证：在设置之前，先确保流不是本地流，并且两个元素不会指向同一个流
                const finalLocalStream = localVideoRef.value?.srcObject as MediaStream | null
                const willBeSameStreamByRef = finalLocalStream && remoteStream === finalLocalStream
                const willBeSameStreamById = finalLocalStream && remoteStream.id === finalLocalStream.id
                
                // 如果会导致两个元素指向同一个流（对象引用相同），拒绝设置
                if (willBeSameStreamByRef) {
                  console.error('❌ [VideoCall] 拒绝设置：远程流会导致和本地流对象引用相同！', {
                    streamId: remoteStream.id,
                    timestamp: new Date().toISOString(),
                    stack: new Error().stack
                  })
                  logger.error('拒绝设置：远程流会导致和本地流对象引用相同！')
                  // 不设置，保持 isRemoteVideoActive = false，占位符继续显示
                  return
                }
                
                // 如果流 ID 相同且对方未接听，拒绝设置（SRS 回环）
                if (willBeSameStreamById && isCalling) {
                  console.error('❌ [VideoCall] 拒绝设置：远程流 ID 和本地流相同且对方未接听！这是 SRS 回环', {
                    streamId: remoteStream.id,
                    callStatus: callStatus.value,
                    timestamp: new Date().toISOString(),
                    stack: new Error().stack
                  })
                  logger.error('拒绝设置：远程流 ID 和本地流相同且对方未接听！这是 SRS 回环')
                  // 不设置，保持 isRemoteVideoActive = false，占位符继续显示
                  return
                }
                
                // 设置远程视频流
                remoteVideoRef.value.srcObject = remoteStream
                // 只有在确认流设置成功且不是本地流后，才设置 isRemoteVideoActive = true
                // 这样可以避免占位符闪烁
                isRemoteVideoActive.value = true
                logger.info('远程视频流已设置到远程视频元素')
                
                // 额外检查：确保本地视频元素没有被错误地设置为远程流
                if (localVideoRef.value && localVideoRef.value.srcObject === remoteStream) {
                  console.error('❌ [VideoCall] 检测到本地视频元素被错误地设置为远程流，正在修复...', {
                    streamId: remoteStream.id,
                    timestamp: new Date().toISOString(),
                    stack: new Error().stack
                  })
                  logger.error('检测到本地视频元素被错误地设置为远程流，正在修复...')
                  // 重新设置本地流
                  if (localStream) {
                    localVideoRef.value.srcObject = localStream
                    isLocalVideoActive.value = true
                    logger.info('已修复：重新设置本地视频流到本地视频元素')
                  }
                }
                
                // 最终验证：确保两个元素指向不同的流对象
                const finalRemoteStream = remoteVideoRef.value.srcObject as MediaStream | null
                const finalLocalStreamAfter = localVideoRef.value?.srcObject as MediaStream | null
                const streamsAreDifferent = finalRemoteStream !== finalLocalStreamAfter
                
                // 如果最终验证失败，清除流并恢复占位符
                if (!streamsAreDifferent && finalRemoteStream) {
                  console.error('❌ [VideoCall] 最终验证失败：远程和本地视频元素指向同一个流对象！清除远程流', {
                    streamId: finalRemoteStream.id,
                    timestamp: new Date().toISOString(),
                    stack: new Error().stack
                  })
                  // 修复：清除远程视频元素的流，恢复占位符
                  remoteVideoRef.value.srcObject = null
                  isRemoteVideoActive.value = false
                }
              } else {
                logger.warn('远程视频元素不可用，无法设置远程视频流')
              }
            } else if (props.isVoiceOnly) {
              // 语音通话模式，只接收音频轨道
              logger.info('语音通话：收到远程音频轨道')
            }
            
            // 注意：状态更新（"已接听"）由轮询机制负责，这里不再直接更新
          },
          onError: (error) => {
            logger.error('拉流错误', error)
            handleError(error.message)
          },
        }
      )

      // 检查拉流管理器是否仍然存在（可能在创建后被清理）
      if (!playManager) {
        logger.warn('拉流管理器在创建后被清理，停止初始化')
        return
      }

      // 添加 transceiver（用于拉流）
      playManager.addTransceivers()

      // 再次检查（可能在添加 transceiver 后被清理）
      if (!playManager) {
        logger.warn('拉流管理器在添加 transceiver 后被清理，停止初始化')
        return
      }

      // 创建并发送拉流 offer
      logger.info('创建拉流 offer...')
      const playOffer = await playManager.createOffer({
        offerToReceiveAudio: true,
        offerToReceiveVideo: !props.isVoiceOnly,
      })

      // 再次检查（可能在创建 offer 后被清理）
      if (!playManager) {
        logger.warn('拉流管理器在创建 offer 后被清理，停止初始化')
        return
      }

      // 发送到 SRS
      logger.info('发送拉流 offer 到 SRS...')
      const playAnswerSdp = await playFromSrs(streamName.value, playOffer)

      // 检查拉流管理器是否仍然存在（可能在异步操作中被清理）
      if (!playManager) {
        logger.warn('拉流管理器已被清理，停止初始化')
        return
      }

      // 设置远程描述
      logger.info('设置拉流远程描述...')
      await playManager.setRemoteDescription({
        type: 'answer',
        sdp: playAnswerSdp,
      })

      // 再次检查（可能在设置远程描述后又被清理）
      if (!playManager) {
        logger.warn('拉流管理器在设置远程描述后被清理')
        return
      }

      // 等待连接建立
      logger.info('等待拉流 ICE 连接建立...')
      await playManager.waitForIceConnection(10000)
      
      // 检查拉流连接状态
      if (!playManager) {
        logger.warn('拉流管理器在等待连接后被清理')
        return
      }

      // 拉流连接成功，但还没收到远程轨道，保持"呼叫中"状态
      // 只有收到远程轨道（onTrack事件）时才更新为"已接听"
      if (callStatus.value === '正在连接...') {
        callStatus.value = '呼叫中'
      }
      isConnecting.value = false
      
      // 注意：不在这里开始计时，只有收到远程轨道（对方真正接听）时才开始计时
    } catch (error: any) {
      logger.error('WebRTC 初始化失败', {
        error: error.message,
        stack: error.stack,
        name: error.name,
      })
      
      // 提供更详细的错误信息
      let errorMessage = error.message || '连接失败，请重试'
      
      // 如果是权限相关错误，提供更具体的提示
      if (error.name === 'NotAllowedError' || error.message?.includes('权限')) {
        errorMessage = '媒体设备权限被拒绝。\n\n' +
          'macOS 系统设置：\n' +
          '1. 打开"系统设置" > "隐私与安全性"\n' +
          '2. 检查"摄像头"和"麦克风"权限\n' +
          '3. 确保 "violet" 应用已被允许访问\n' +
          '4. 如果应用不在列表中，请重启应用后再次尝试'
      } else if (error.name === 'NotFoundError' || error.message?.includes('找不到')) {
        errorMessage = '未找到媒体设备。\n\n' +
          '请确保：\n' +
          '1. 摄像头和麦克风已正确连接\n' +
          '2. 设备未被其他应用占用\n' +
          '3. 设备驱动已正确安装'
      } else if (error.name === 'NotReadableError' || error.message?.includes('无法读取')) {
        errorMessage = '无法读取媒体设备。\n\n' +
          '可能的原因：\n' +
          '1. 设备正被其他应用使用\n' +
          '2. 设备驱动问题\n' +
          '3. 系统权限问题\n\n' +
          '请关闭其他可能使用摄像头/麦克风的应用后重试'
      } else if (error.message?.includes('拉流连接失败') || error.message?.includes('流不存在') || (error.message?.includes('SRS 返回错误码') && error.message?.includes('400'))) {
        errorMessage = '无法连接到对方的音视频流。\n\n' +
          '可能的原因：\n' +
          '1. 对方尚未接听通话\n' +
          '2. 对方的推流连接失败\n' +
          '3. 流尚未发布\n' +
          '4. 网络连接问题\n\n' +
          '请确保对方已接听通话，或稍后重试'
      } else if (error.message?.includes('推流连接失败') || error.message?.includes('推流请求失败') || error.message?.includes('SRS 返回错误码: 400')) {
        // 检查是否是流忙碌错误
        if (error.message?.includes('已存在') || error.message?.includes('忙碌') || error.message?.includes('busy')) {
          errorMessage = '推流连接失败：流已存在或忙碌。\n\n' +
            '可能的原因：\n' +
            '1. 之前的连接未正确关闭\n' +
            '2. 重复推流请求\n\n' +
            '建议：关闭当前通话窗口，等待几秒后重新尝试'
        } else if (error.message?.includes('400')) {
          errorMessage = '推流连接失败：SRS 返回错误码 400。\n\n' +
            '可能的原因：\n' +
            '1. 流已存在（重复推流）\n' +
            '2. 参数格式错误\n' +
            '3. 流名称冲突\n\n' +
            '建议：关闭当前通话窗口，等待几秒后重新尝试'
        } else {
          errorMessage = '无法建立推流连接。\n\n' +
            '可能的原因：\n' +
            '1. SRS 服务器连接失败\n' +
            '2. 网络连接问题\n' +
            '3. 流名称冲突\n\n' +
            '请检查网络连接或稍后重试'
        }
      } else if (error.message?.includes('网络') || error.message?.includes('fetch') || error.message?.includes('Network')) {
        errorMessage = '网络连接失败。\n\n' +
          '请检查：\n' +
          '1. 网络连接是否正常\n' +
          '2. 服务器是否可访问\n' +
          '3. 防火墙设置\n\n' +
          '如果问题持续，请联系技术支持'
      }
      
      handleError(errorMessage)
    } finally {
      // 重置初始化标志
      isInitializing = false
    }
  }

  // 切换静音（主流IM方案：直接控制轨道enabled属性，不重新连接）
  const toggleMute = () => {
    // 优先使用缓存的轨道（最快、最可靠）
    let audioTracks: MediaStreamTrack[] = cachedAudioTracks.filter(track => track.readyState !== 'ended')
    
    // 如果缓存为空，尝试从 localStream 获取
    if (audioTracks.length === 0 && localStream) {
      audioTracks = localStream.getAudioTracks()
      // 更新缓存
      cachedAudioTracks = audioTracks
    }
    
    // 如果还是没有，尝试从 publishManager 获取（备用方案）
    if (audioTracks.length === 0 && publishManager) {
      const pc = publishManager.getPeerConnection()
      const senders = pc.getSenders()
      audioTracks = senders
        .map(sender => sender.track)
        .filter((track): track is MediaStreamTrack => track !== null && track.kind === 'audio' && track.readyState !== 'ended')
      // 更新缓存
      if (audioTracks.length > 0) {
        cachedAudioTracks = audioTracks
      }
    }
    
    if (audioTracks.length > 0) {
      const newMutedState = !isMuted.value
      isMuted.value = newMutedState
      
      // 直接控制轨道状态（主流IM方案）
      audioTracks.forEach(track => {
        track.enabled = !newMutedState
      })
      
      logger.info(`静音状态: ${newMutedState ? '已静音' : '已取消静音'} (${audioTracks.length} 个音频轨道)`)
    } else {
      logger.warn('无法切换静音：找不到可用的音频轨道', {
        cachedCount: cachedAudioTracks.length,
        hasLocalStream: !!localStream,
        hasPublishManager: !!publishManager,
      })
    }
  }

  // 切换视频（主流IM方案：直接控制轨道enabled属性，不重新连接）
  const toggleVideo = () => {
    // 在语音通话模式下，不应该调用此函数
    if (props.isVoiceOnly) {
      logger.warn('在语音通话模式下尝试切换视频')
      return
    }
    
    // 优先使用缓存的轨道（最快、最可靠）
    let videoTracks: MediaStreamTrack[] = cachedVideoTracks.filter(track => track.readyState !== 'ended')
    
    // 如果缓存为空，尝试从 localStream 获取
    if (videoTracks.length === 0 && localStream) {
      videoTracks = localStream.getVideoTracks()
      // 更新缓存
      cachedVideoTracks = videoTracks
    }
    
    // 如果还是没有，尝试从 publishManager 获取（备用方案）
    if (videoTracks.length === 0 && publishManager) {
      const pc = publishManager.getPeerConnection()
      const senders = pc.getSenders()
      videoTracks = senders
        .map(sender => sender.track)
        .filter((track): track is MediaStreamTrack => track !== null && track.kind === 'video' && track.readyState !== 'ended')
      // 更新缓存
      if (videoTracks.length > 0) {
        cachedVideoTracks = videoTracks
      }
    }
    
    if (videoTracks.length > 0) {
      const newVideoOffState = !isVideoOff.value
      isVideoOff.value = newVideoOffState
      
      // 直接控制轨道状态（主流IM方案）
      videoTracks.forEach(track => {
        track.enabled = !newVideoOffState
      })
      
      logger.info(`视频状态: ${newVideoOffState ? '已关闭' : '已开启'} (${videoTracks.length} 个视频轨道)`)
    } else {
      logger.warn('无法切换视频：找不到可用的视频轨道', {
        cachedCount: cachedVideoTracks.length,
        hasLocalStream: !!localStream,
        hasPublishManager: !!publishManager,
      })
    }
  }

  // 切换到语音模式（关闭视频）
  const switchToVoiceOnly = async () => {
    try {
      // 如果正在初始化，等待完成
      if (isInitializing) {
        logger.info('正在初始化中，等待完成后再切换模式')
        // 等待最多5秒
        let waitCount = 0
        while (isInitializing && waitCount < 50) {
          await new Promise(resolve => setTimeout(resolve, 100))
          waitCount++
        }
      }
      
      // 停止当前连接
      cleanup()
      
      // 等待清理完成
      await new Promise(resolve => setTimeout(resolve, 300))
      
      // 更新 props（通过 emit 通知父组件）
      emit('switch-mode', { isVoiceOnly: true })
      
      // 重新初始化（语音模式）
      // 注意：这里需要父组件更新 props.isVoiceOnly 后才会重新初始化
    } catch (error: any) {
      logger.error('切换到语音模式失败', error)
      handleError('切换到语音模式失败')
    }
  }

  // 升级到视频模式（开启视频）
  const switchToVideo = async () => {
    try {
      // 如果正在初始化，等待完成
      if (isInitializing) {
        logger.info('正在初始化中，等待完成后再切换模式')
        // 等待最多5秒
        let waitCount = 0
        while (isInitializing && waitCount < 50) {
          await new Promise(resolve => setTimeout(resolve, 100))
          waitCount++
        }
      }
      
      // 停止当前连接
      cleanup()
      
      // 等待清理完成
      await new Promise(resolve => setTimeout(resolve, 300))
      
      // 更新 props（通过 emit 通知父组件）
      emit('switch-mode', { isVoiceOnly: false })
      
      // 重新初始化（视频模式）
      // 注意：这里需要父组件更新 props.isVoiceOnly 后才会重新初始化
    } catch (error: any) {
      logger.error('升级到视频模式失败', error)
      handleError('升级到视频模式失败')
    }
  }

  // 结束通话
  const handleEndCall = async () => {
    // 发送通话取消消息，通知对方（移动端等）通话已结束
    try {
      const { sendCallCancel } = await import('../../stores/chat-message-sender')
      const { ref } = await import('vue')
      // 创建一个 ref 包装 currentUserId
      const currentUserIdRef = ref(props.currentUserId)
      await sendCallCancel(
        props.targetUserId,
        currentUserIdRef
      )
      logger.info('已发送通话取消消息')
    } catch (error: any) {
      logger.error('发送通话取消消息失败:', error)
      // 即使发送失败，也继续执行挂断流程
    }
    
    // 停止呼叫铃声
    stopCallRingtone()
    cleanup()
    emit('call-ended')
    isOpen.value = false
  }

  // 关闭对话框
  const handleClose = () => {
    handleEndCall()
  }

  // 错误处理
  const handleError = (message: string) => {
    // 过滤掉不需要显示的错误提示
    if (message.includes('推流管理器已被清理')) {
      logger.info('推流管理器已被清理，静默处理')
      isConnecting.value = false
      return
    }
    
    // 停止呼叫铃声
    stopCallRingtone()
    
    $q.notify({
      type: 'negative',
      message,
      position: 'top',
    })
    isConnecting.value = false
    callStatus.value = '连接失败'
    setTimeout(() => {
      handleEndCall()
    }, 2000)
  }

  // 开始通话计时
  const startCallTimer = () => {
    callDuration.value = 0
    if (callDurationTimer) {
      clearInterval(callDurationTimer)
    }
    callDurationTimer = window.setInterval(() => {
      callDuration.value++
    }, 1000)
  }

  // 停止通话计时
  const stopCallTimer = () => {
    if (callDurationTimer) {
      clearInterval(callDurationTimer)
      callDurationTimer = null
    }
    callDuration.value = 0
  }

  // 开始检查播放者（轮询检测对方是否拉流）
  const startPlayerCheck = () => {
    // 如果已经有轮询在运行，先停止
    stopPlayerCheck()
    
    logger.info('开始轮询检查播放者（检测对方是否拉流）')
    
    // 每2秒检查一次是否有播放者
    playerCheckInterval = window.setInterval(async () => {
      try {
        // 如果状态已经是"已接听"，停止轮询
        if (callStatus.value === '已接听') {
          stopPlayerCheck()
          return
        }
        
        // 如果通话已关闭，停止轮询
        if (!isOpen.value || !publishManager) {
          stopPlayerCheck()
          return
        }
        
        // 在对方未接听时，确保远程视频元素没有流
        // 关键：只有在对方未接听（callStatus === '呼叫中'）时才清除流
        // 如果已经接听（callStatus === '已接听'），不要清除流
        if (callStatus.value === '呼叫中' && remoteVideoRef.value) {
          const remoteSrcObject = remoteVideoRef.value.srcObject as MediaStream | null
          const localSrcObject = localVideoRef.value?.srcObject as MediaStream | null
          
          // 关键：通过对象引用比较和 ID 比较
          const remoteIsLocalStreamByRef = localStream && remoteSrcObject === localStream
          const remoteIsLocalStreamById = localStream && remoteSrcObject && remoteSrcObject.id === localStream.id
          
          // 如果远程视频元素有流，且（对象引用相同 或 ID 相同），在对方未接听时都应该清除
          // 这是 SRS 回环的情况
          if (remoteSrcObject && (remoteIsLocalStreamByRef || remoteIsLocalStreamById)) {
            console.warn('⚠️ [VideoCall] 轮询检查：检测到对方未接听但远程视频元素有流（SRS 回环），清除它', {
              streamId: remoteSrcObject.id,
              isLocalStreamByRef: remoteIsLocalStreamByRef, // 对象引用比较
              isLocalStreamById: remoteIsLocalStreamById, // ID 比较
              timestamp: new Date().toISOString(),
              callStatus: callStatus.value
            })
            logger.warn('检测到对方未接听但远程视频元素有流（SRS 回环），清除它')
            // 清除流
            remoteVideoRef.value.srcObject = null
            // 使用 setTimeout 确保状态更新不会冲突
            setTimeout(() => {
              if (callStatus.value === '呼叫中' && !remoteVideoRef.value?.srcObject) {
                isRemoteVideoActive.value = false
              }
            }, 0)
          } else if (remoteSrcObject && remoteIsLocalStreamById) {
            // 即使对象引用不同，但 ID 相同，在对方未接听时也应该清除（SRS 回环）
            console.warn('⚠️ [VideoCall] 轮询检查：检测到远程流 ID 和本地流相同（SRS 回环），清除它', {
              streamId: remoteSrcObject.id,
              timestamp: new Date().toISOString(),
              callStatus: callStatus.value
            })
            remoteVideoRef.value.srcObject = null
            setTimeout(() => {
              if (callStatus.value === '呼叫中' && !remoteVideoRef.value?.srcObject) {
                isRemoteVideoActive.value = false
              }
            }, 0)
          } else if (isRemoteVideoActive.value && !remoteSrcObject) {
            // 如果远程视频元素没有流，但 isRemoteVideoActive 还是 true，修正状态
            // 这可能是之前的状态没有正确更新
            isRemoteVideoActive.value = false
          }
          
          // 额外检查：确保远程视频元素没有被错误地设置为本地流（通过对象引用和 ID 比较）
          if ((remoteIsLocalStreamByRef || remoteIsLocalStreamById) && localStream) {
            console.error('❌ [VideoCall] 轮询检查：检测到远程视频元素被错误地设置为本地流，正在修复...', {
              streamId: localStream.id,
              isLocalStreamByRef: remoteIsLocalStreamByRef,
              isLocalStreamById: remoteIsLocalStreamById,
              timestamp: new Date().toISOString(),
              stack: new Error().stack
            })
            logger.error('轮询检查：检测到远程视频元素被错误地设置为本地流，正在修复...')
            remoteVideoRef.value.srcObject = null
            isRemoteVideoActive.value = false
            
            // 确保本地视频元素有本地流
            if (localVideoRef.value && localStream && localVideoRef.value.srcObject !== localStream) {
              logger.warn('轮询检查：本地视频元素没有本地流，重新设置...')
              localVideoRef.value.srcObject = localStream
              isLocalVideoActive.value = true
            }
          }
        }
        
        // 查询是否有播放者
        const result = await checkStreamPlayers(streamName.value)
        logger.info('播放者检查结果:', result)
        
        if (result.has_players && result.player_count > 0) {
          // 检测到有播放者，表示对方已拉流（已接听）
          logger.info(`检测到 ${result.player_count} 个播放者，对方已接听`)
          callStatus.value = '已接听'
          isConnecting.value = false
          
          // 停止呼叫铃声（对方已接听）
          stopCallRingtone()
          
          // 停止轮询
          stopPlayerCheck()
          
          // 停止超时定时器（对方已接听，不需要超时挂断）
          stopCallTimeout()
          
          // 开始计时
          startCallTimer()
        }
      } catch (error) {
        // 查询失败不影响通话，只记录日志
        logger.warn('检查播放者失败:', error)
      }
    }, 2000) // 每2秒检查一次
  }

  // 停止检查播放者
  const stopPlayerCheck = () => {
    if (playerCheckInterval) {
      clearInterval(playerCheckInterval)
      playerCheckInterval = null
      logger.info('停止播放者检查轮询')
    }
  }

  // 启动通话超时定时器（33秒后如果没接听，自动挂断）
  const startCallTimeout = () => {
    // 如果已经有超时定时器在运行，先停止
    stopCallTimeout()
    
    logger.info('启动通话超时定时器（33秒）')
    
    callTimeoutTimer = window.setTimeout(() => {
      // 如果33秒后状态还是"呼叫中"，说明对方没接听，自动挂断
      if (callStatus.value === '呼叫中') {
        logger.warn('通话超时（33秒内未接听），自动挂断')
        handleEndCall()
      }
    }, 33 * 1000) // 33秒
  }

  // 停止通话超时定时器
  const stopCallTimeout = () => {
    if (callTimeoutTimer) {
      clearTimeout(callTimeoutTimer)
      callTimeoutTimer = null
      logger.info('停止通话超时定时器')
    }
  }

  // 格式化通话时长
  const formatCallDuration = (seconds: number): string => {
    const hours = Math.floor(seconds / 3600)
    const minutes = Math.floor((seconds % 3600) / 60)
    const secs = seconds % 60
    
    if (hours > 0) {
      return `${hours}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`
    }
    return `${minutes}:${String(secs).padStart(2, '0')}`
  }

  // 清理资源
  const cleanup = () => {
    stopStreamCheck() // 停止视频流检查
    stopCallTimer()
    stopPlayerCheck() // 停止播放者检查轮询
    stopCallTimeout() // 停止通话超时定时器
    // 停止呼叫铃声
    stopCallRingtone()
    
    if (localStream) {
      localStream.getTracks().forEach(track => track.stop())
      localStream = null
    }

    if (publishManager) {
      publishManager.close()
      publishManager = null
    }

    if (playManager) {
      playManager.close()
      playManager = null
    }

    // 清除本地视频元素
    if (localVideoRef.value) {
      if (localVideoRef.value.srcObject) {
        const stream = localVideoRef.value.srcObject as MediaStream
        stream.getTracks().forEach(track => {
          // 轨道会在 localStream.stop() 中停止，这里只清除引用
        })
      }
      localVideoRef.value.srcObject = null
      logger.info('已清除本地视频元素的流')
    }

    // 清除远程视频元素
    if (remoteVideoRef.value) {
      if (remoteVideoRef.value.srcObject) {
        const stream = remoteVideoRef.value.srcObject as MediaStream
        stream.getTracks().forEach(track => {
          // 远程流的轨道会在 playManager.close() 中停止，这里只清除引用
        })
      }
      remoteVideoRef.value.srcObject = null
      logger.info('已清除远程视频元素的流')
    }

    isLocalVideoActive.value = false
    isRemoteVideoActive.value = false
    
    // 清理缓存的轨道引用
    cachedAudioTracks = []
    cachedVideoTracks = []
  }

  // 监听对话框打开
  watch(isOpen, (newValue) => {
    if (newValue) {
      initWebRTC()
      // 开始定期检查视频流分配
      startStreamCheck()
    } else {
      stopStreamCheck()
      cleanup()
    }
  })

  // 监听模式切换（语音/视频）
  watch(() => props.isVoiceOnly, (newValue, oldValue) => {
    // 如果模式发生变化且窗口已打开，重新初始化 WebRTC
    if (isOpen.value && newValue !== oldValue && oldValue !== undefined) {
      logger.info(`模式切换: ${oldValue ? '语音' : '视频'} -> ${newValue ? '语音' : '视频'}`)
      cleanup()
      // 延迟重新初始化，确保清理完成
      setTimeout(() => {
        if (isOpen.value) {
          initWebRTC()
        }
      }, 300)
    }
  })

  // 监听远程视频激活状态，确保在未激活时清除远程视频流
  watch(isRemoteVideoActive, (newValue, oldValue) => {
    const currentLocalStream: MediaStream | null = localStream
    
    if (!newValue && remoteVideoRef.value) {
      // 如果远程视频未激活，清除远程视频流的 srcObject
      if (remoteVideoRef.value.srcObject) {
        const currentStream = remoteVideoRef.value.srcObject as MediaStream
        logger.info('远程视频未激活，清除远程视频流')
        remoteVideoRef.value.srcObject = null
      }
      
      // 额外检查：确保远程视频元素没有被错误地设置为本地流
      if (currentLocalStream && remoteVideoRef.value.srcObject === currentLocalStream) {
        const streamId = (currentLocalStream as MediaStream).id
        console.error('❌ [VideoCall] watch isRemoteVideoActive: 检测到远程视频元素被错误地设置为本地流，正在修复...', {
          streamId: streamId,
          timestamp: new Date().toISOString(),
          stack: new Error().stack
        })
        logger.error('watch isRemoteVideoActive: 检测到远程视频元素被错误地设置为本地流，正在修复...')
        remoteVideoRef.value.srcObject = null
        isRemoteVideoActive.value = false
      }
    }
  })
  
  // 定期检查并修复视频流分配（防止任何意外的流分配错误）
  let streamCheckInterval: number | null = null
  const startStreamCheck = () => {
    if (streamCheckInterval) {
      clearInterval(streamCheckInterval)
    }
    
    
    streamCheckInterval = window.setInterval(() => {
      if (!isOpen.value) {
        if (streamCheckInterval) {
          clearInterval(streamCheckInterval)
          streamCheckInterval = null
        }
        return
      }
      
      // 只在对方未接听时检查
      if (callStatus.value === '呼叫中' && localStream) {
        const remoteSrcObject = remoteVideoRef.value?.srcObject as MediaStream | null
        const localSrcObject = localVideoRef.value?.srcObject as MediaStream | null
        
        // 检查远程视频元素是否被错误地设置为本地流
        if (remoteVideoRef.value && remoteVideoRef.value.srcObject === localStream) {
          console.error('❌ [VideoCall] 定期检查：检测到远程视频元素被错误地设置为本地流，正在修复...', {
            streamId: localStream.id,
            timestamp: new Date().toISOString(),
            stack: new Error().stack
          })
          logger.error('定期检查：检测到远程视频元素被错误地设置为本地流，正在修复...')
          remoteVideoRef.value.srcObject = null
          isRemoteVideoActive.value = false
        }
        
        // 确保本地视频元素有本地流
        if (localVideoRef.value && localVideoRef.value.srcObject !== localStream) {
          logger.warn('定期检查：本地视频元素没有本地流，重新设置...')
          localVideoRef.value.srcObject = localStream
          isLocalVideoActive.value = true
        }
        
        // 每 10 次检查记录一次状态（避免日志过多）
        // 已移除调试日志，仅保留验证相关的错误和警告日志
      }
    }, 500) // 每 500ms 检查一次
  }
  
  const stopStreamCheck = () => {
    if (streamCheckInterval) {
      clearInterval(streamCheckInterval)
      streamCheckInterval = null
    }
  }

  // 监听本地视频激活状态，确保在未激活时清除本地视频流（仅在视频关闭时）
  watch([isLocalVideoActive, isVideoOff], ([localActive, videoOff]) => {
    if ((!localActive || videoOff) && localVideoRef.value && localStream) {
      // 如果本地视频未激活或已关闭，但流仍然存在，确保视频元素正确显示占位符
      // 注意：这里不清除 srcObject，因为流仍然需要用于推流
      // 只是通过 CSS 的 opacity 和占位符来控制显示
      logger.info('本地视频未激活或已关闭，但流仍然保留用于推流')
    }
  })

  // 组件卸载时清理
  onUnmounted(() => {
    cleanup()
  })

  return {
    // 状态
    isOpen,
    isConnecting,
    loadingText,
    callStatus,
    isMuted,
    isVideoOff,
    isLocalVideoActive,
    isRemoteVideoActive,
    // 方法
    toggleMute,
    toggleVideo,
    switchToVoiceOnly,
    switchToVideo,
    handleEndCall,
    handleClose,
  }
}


